Skip to content

fix: drop \def\v0 / \def\v1, which broke \v for every other argument - #87

Merged
d-morrison merged 1 commit into
mainfrom
86-fix-delimited-v-defs
Sep 10, 2026
Merged

d-morrison merged 1 commit into
mainfrom
86-fix-delimited-v-defs

Conversation

@d-morrison

@d-morrison d-morrison commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What was wrong

TeX has no multi-token control sequences, so \def\v0{\vec{0}} (line 68) and
\def\v1{\vec{1}} (line 375) never defined macros named \v0 and \v1. Each defined \v
with a delimited parameter whose delimiter is the literal digit. \def\v1 came last, so
the surviving definition of \v matched only a following literal 1 — silently overriding
\renewcommand{\v}[1]{\vecf{#1}} at line 356.

So \v1 worked by accident of ordering, while \v0 and the documented one-argument form
\v{x} both failed with Use of \v doesn't match its definition.

It took down d-morrison/rme's book build:
run 34375808884.

The change

Remove both pseudo-definitions. \renewcommand{\v}[1]{\vecf{#1}} becomes the only
definition of \v, so \v0, \v1 and \v{x} all expand as ordinary one-argument
invocations. \vone joins the existing \vzero for consumers preferring a named form, and
interpretations.tsv is updated to match.

Verification

Appending $\v0$ $\v1$ $\v{x}$ to each version of macros.qmd and running
quarto pandoc -t latex (pandoc 3.8.3, whose latex_macros extension expands macros defined
in the same document):

\v0 \v1 \v{x}
no definitions present \v0 \v1 \v{x}
macros.qmd before \v0 \tilde{1} \v{x}
macros.qmd after \tilde{0} \tilde{1} \tilde{x}

The middle row is the bug: only \v1 expands. The top row shows what an unexpanded macro
looks like, which is what the middle row's \v0 and \v{x} are — and those are exactly the
tokens LuaLaTeX chokes on.

Also checked:

  • Existing consumer usages \tp{\v1}\vx and \pi^{\v1 \cdot \vy} (from d-morrison/rme's
    _sec_linear_algebra.qmd, _sec_logistic_score_fn.qmd, _sec_binary_likelihood_no_covs.qmd)
    produce byte-identical output before and after.
  • Both rendering paths: macros-table.qmd rewrites only \providecommand lines into \def
    for MathJax, so the \renewcommand{\v}[1] passes through unmodified and works there too.
  • macros-table.qmd's own parse_macros() + merge step run against the changed files:
    793 macros, 0 missing interpretation rows, 0 orphaned TSV rows.

Closes #86

🤖 Generated with Claude Code

…ument

TeX has no multi-token control sequences, so `\def\v0{\vec{0}}` and
`\def\v1{\vec{1}}` never defined macros named `\v0` and `\v1`. Each
defined `\v` with a *delimited* parameter whose delimiter is the
literal digit, and `\def\v1` came last, so the surviving definition of
`\v` matched only a following literal `1`.

That silently overrode `\renewcommand{\v}[1]{\vecf{#1}}`, so `\v1`
worked by accident of ordering while `\v0` and the documented
one-argument form `\v{x}` both failed with

    Use of \v doesn't match its definition.

It took down d-morrison/rme's book build:
https://github.com/d-morrison/rme/actions/runs/34375808884/job/102548187534

Removing both pseudo-definitions leaves `\renewcommand{\v}[1]` as the
only definition of `\v`, so `\v0`, `\v1` and `\v{x}` all expand as
one-argument invocations. `\vone` joins the existing `\vzero` for
consumers that prefer a named form.

Verified with pandoc 3.8.3, whose `latex_macros` extension expands macros
defined in the same document. Appending `$\v0$ $\v1$ $\v{x}$` to each
version of `macros.qmd` and running `quarto pandoc -t latex` gives:

    no definitions present   \v0        \v1        (\v{x} absent)
    macros.qmd before        \v0        \tilde{1}  \v{x}
    macros.qmd after         \tilde{0}  \tilde{1}  \tilde{x}

The middle row is the bug: only `\v1` expands, because the delimited
`\def\v1` is the surviving definition of `\v`. The top row shows what
an unexpanded macro looks like, which is what the middle row's `\v0` and
`\v{x}` are -- and those are exactly the tokens LuaLaTeX then chokes on.
The bottom row is the fix.

Existing consumer usages `\tp{\v1}\vx` and `\pi^{\v1 \cdot \vy}`
expand identically before and after.

Closes #86

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 9, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@d-morrison
d-morrison requested a lite review from Copilot September 9, 2026 19:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@d-morrison

Copy link
Copy Markdown
Owner Author

Claimed by a Claude Code CLI session — driving this PR to a clean review and green CI.

Filed and fixed from the consumer side: this defect broke d-morrison/rme's book build, worked around there in d-morrison/rme#1159 by switching to \vzero. That workaround stays valid after this merges; no submodule bump is required to keep it working.

Posted by Claude Code (AI agent) --- not written by a human.

@d-morrison
d-morrison requested a lite review from Copilot September 9, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@d-morrison

Copy link
Copy Markdown
Owner Author

Corrected an error in this PR's own table, caught by the @claude reviewer on Morrison-Lab/ai-config#3468 while fact-checking a UMS entry that quoted it.

The null row's \v{x} cell read , which reads as a claim that nothing is emitted. It was really a placeholder for "I did not test that cell": my null-row command was printf '\n$\v0$ $\v1$\n', with no \v{x} in it. Measured properly:

$ printf 'Bare: $\v0$ $\v1$ $\v{x}$\n' > nodef.md
$ quarto pandoc nodef.md -t latex
Bare: \(\v0\) \(\v1\) \(\v{x}\)

So \v{x} passes through unexpanded like the other two, and the cell now says \v{x}. The reviewer reproduced all three rows against pandoc 3.1.3, 3.8.3 and Quarto 1.8.25's bundled 3.6.3, on this branch and its parent, and its results agree with the corrected table.

Nothing about the fix or the other five cells changes — the discriminating middle row is untouched.

Worth recording plainly: this is a table written to correct a reviewer's mis-measurement, which itself shipped with a mis-measured cell, for the same underlying reason — a test that omitted one of the things the claim covered.

Posted by Claude Code (AI agent) --- not written by a human.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

👀 Claude Review Dispatchedrun reviewing PR #87 at 2426617.

@github-actions

Copy link
Copy Markdown
Contributor

Claude finished reviewView run


Tabs are correct. Everything checks out — the diff is small, well-scoped, and the TeX reasoning in the PR description is technically sound (control-word tokenization in TeX does terminate at a digit, so \def\v0/\def\v1 never created macros named \v0/\v1; they instead redefined \v with a delimited parameter, which explains the reported breakage). I verified:

  • No other references to \v0, \v1, or the old \vzero alias-text remain anywhere in the repo (README, CONTRIBUTING, index.qmd, demo files, macros-table.qmd).
  • interpretations.tsv and macros.qmd stay in sync: \vzero/\vone are defined in both files, with correct tab-separated formatting.
  • macros-table.qmd's \def parsing regex (^\\def\\([^{ \t]+)\{(.*)\}\s*$) matches \vone fine, so the new macro will still show up correctly in the generated table.
  • \vone (\vec{1}) is defined analogously to the pre-existing \vzero (\vec{0}), and both correctly resolve through the later \renewcommand{\vec}[1]{\tilde{#1}} to \tilde{1}/\tilde{0}, consistent with \v1's \vecf{1}\tilde{1} path described in the PR's verification table.
  • quarto/pandoc weren't available in this sandbox to re-run the author's verification table, but the TeX semantics described are correct and the diff itself is minimal and directly addresses the described bug.

No lab-manual concerns: this is a pure .qmd/.tsv data fix, no R code, no function-length or documentation requirements apply, and no raw HTML/markdown style issues in the touched lines.

I did not find any evidence of hallucinated APIs/functions — the PR only edits macro/text definitions and a TSV mapping, no external calls or citations to fabricate.

Verdict

Ready for merge

Structured Review Data (JSON)
{
  "schema_version": "1.1",
  "reviewer": "claude",
  "commit_sha": "2426617cf9c2cdfe67fe665a76ddccca3997c76f",
  "verdict": "CLEAN",
  "findings": [],
  "detailed_assessment": "Checked macros.qmd and interpretations.tsv for leftover \\v0/\\v1 references and confirmed macros-table.qmd's parsing regex still matches the new \\vone definition correctly.",
  "holistic_assessment": "Fix is narrowly scoped, restores documented \\v{x} one-argument usage, keeps interpretations.tsv synchronized with macros.qmd, and introduces no regression risk to other consumers."
}

Reviewed commit: 2426617

@github-actions

Copy link
Copy Markdown
Contributor

💰 Cost: $0.2405 (review) — run

@d-morrison
d-morrison merged commit 36b4cb5 into main Sep 10, 2026
1 check passed
@d-morrison
d-morrison deleted the 86-fix-delimited-v-defs branch September 10, 2026 07:05
d-morrison added a commit to Morrison-Lab/ai-config that referenced this pull request Sep 10, 2026
…port, or phrased outside the guard vocabulary (#3475)

* Stop hook: fix subagent-relevance scoping, source_note derivation, and citations (ai-config#3472)

Extends no-incomplete-check-enumeration.py to WARN on broader merge-readiness
vocabulary and on a terminal claim resting solely on a dispatched subagent's
own report. Addresses five findings from an adversarial review of the
staged extension:

1. BLOCKING REGRESSION: subagent relevance was scoped to the whole
   transcript, so any unrelated earlier Agent dispatch silently downgraded
   the original BLOCK case to a WARN. Now scoped to the claim's own
   evidence window (after the CI reading in play, or a matching PR target).
2. The WARN message's source_note was chosen by an independent predicate
   that could disagree with what actually fired, producing a false
   explanation. Now derived directly from the firing condition.
3. Wrong citations (ai-config#3468, d-morrison/macros#87 as if they were
   write-ups). Fixed to cite ai-config#3472 as the write-up, naming the
   other two as the subject PRs where the claims were made in chat.
4. RX_DECLARE_MERGE_READY had no PR/git anchoring, so an ordinary
   data-frame merge sentence could false-positive. Now requires a nearby
   #N/PR/branch token.
5. Removed unreachable dead-code branch in the test suite's classify().

Registered in hooks.json but NOT activated as a new hook -- it already
governs an existing, currently-active hook, so no separate activation gate
applies; this PR only changes that hook's already-active behavior on merge.

Closes #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: isolate the matches_target arm of _relevant_last_subagent

Review of d1673c2 found the branch untested: deleting it outright
(`matches_target = False`) left all 34 cases passing, so its documented
behaviour was unverified and could have been inverted or reading the
wrong PR set without the suite noticing.

Every existing subagent-relevance case varies only the TIMING of the
dispatch, so all of them are decided by `in_window` alone. Nothing
exercised the other arm.

Adds SAME_PR_AGENT_DISPATCH / SAME_PR_AGENT_REPORT and a case pairing
them: a subagent dispatched about the SAME PR as the claim, landing
BEFORE the CI reading. It is outside the timing window, so `in_window`
is False, and only `matches_target` can carry it. Its shape is identical
to the unrelated-#9999 case directly above, which blocks -- the PR
reference is the single difference between them.

Mutation-checked, which is the point of the case:

    matches_target = False   ->  34/35, FAIL warn (got block)
    restored                 ->  35/35

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): stop the WARN text asserting things the transcript contradicts

Three fixes from the review of 1e3bfcf (#3475), plus the generated
plugin copy that CI caught out of date.

**Finding 2 -- the scoping fix was inert wherever it mattered most.**
`in_window = idx > last_partial` reads `idx > -1` when there is no CI
reading anywhere in the transcript, which is true for every event. So a
transcript with no partial reading re-admitted the entire transcript
through the very branch written to scope it, and an unrelated dispatch
for #9999 made a later claim about #123 look subagent-sourced. Requires
a reading to exist before there is a window to be inside.

**Finding 1 -- the WARN blamed the subagent for a push's staleness.**
The subagent reason fired whenever a relevant event existed, without
asking whether the subagent is why the claim is uncovered. On
AGENT_DISPATCH, AGENT_REPORT, CHECKER, PUSH the session *did* run the
complete instrument, after the report -- so "not a reading you ran
yourself" is false, and the push is the real cause. Gated on
`last_subagent > last_complete`, with a distinct push-staleness reason
for the other case.

This suite had that scenario baked in backwards: its CONTENT_CASES
asserted the subagent text was correct there. Corrected, and paired with
a case where the report IS the last evidence, so each reason is pinned
to the situation it is true in.

Mutation-checked, each against the restored 37/37:

    in_window back to `idx > last_partial`      ->  36/37
    subagent reason ungated                     ->  36/37
    push-staleness reason removed               ->  36/37

Also regenerates skills/ai-config-hooks/hooks/hooks.json, which
`gen-hooks-plugin.py --check` failed on in CI.

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): give the tie a reason, so no WARN ships an empty explanation

Round-2 review of e9eaeb5 found the same defect class the previous
commit claimed to have closed, one case further out.

A push and a complete instrument read in the SAME turn -- two tool_use
blocks in one message, or one `git push && check-pr-fully-clean.py`
command -- land on the same transcript index. Both reason guards are
strict `>`, so at a tie neither fires; with `hit_core` true the
vocabulary reason does not fire either, and `source_note` comes out
empty. The message then had a blank gap where its explanation belongs,
and its headline asserted no instrument run existed "since the most
recent push" when one had run in that very turn.

Three changes:

- A tie reason. It says what is actually true: the transcript cannot
  order two events in one turn, so re-run the instrument alone.
- The headline now says "postdates" rather than "since", which stays
  true at a tie.
- The push reason drops its `last_complete >= last_subagent` conjunct.
  Paired with the subagent guard's `>`, that made the two mutually
  exclusive, so the adjacent comment's "both apply when both reasons are
  in play" could never happen. Both can now fire when both are true.

Also drops the `and last_partial >= 0` conjunct from
`is_original_ci_case`, which the guard above already forces, and covers
`your call to merge`, an alternative deletable with the suite green.

Mutation-checked against the restored 39/39:

    tie reason removed                ->  38/39
    `your call to merge` removed      ->  38/39

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): derive the WARN reason from an argmax, not from independent guards

Three review rounds each found another transcript where the WARN text
asserted something the transcript contradicts, and each was patched with
one more guard. Round 3's was a partial CI reading landing AFTER a
subagent's report: `last_subagent > last_complete` held, so the message
still said the subagent's report was "the most recent evidence in this
transcript", while a fresher reading sat right there.

Tightening that guard to `last_subagent > max(last_complete,
last_partial)` just resurfaces round 2's empty-note bug on the same
transcript -- no reason fires at all. That is the tell: the shape is
wrong, not the thresholds. Independent predicates over four indices have
no reason to be exhaustive, and nothing makes them agree with each other.

So derive the reason from which evidence is actually newest:

    evidence = [(last_subagent, "subagent"), (last_partial, "partial"),
                (last_push, "push"), (last_complete, "complete")]
    newest = max(v for v, _ in evidence)
    kinds = {k for v, k in evidence if v == newest and v >= 0}

A complete read tied with something it would have to postdate is the tie
case. Otherwise every kind at the newest index contributes its reason, so
the note names exactly what the transcript holds -- and ties between two
non-complete kinds name both rather than picking one. An argmax cannot be
empty and cannot name evidence that is not the newest, so both failure
modes are structural rather than patched.

Adds the missing partial-reading reason, which had no WARN-path text at
all -- the gap that let round 3's transcript fall through to the subagent.

Verified against the reviewer's own reproducer: subagent report, then
`gh pr checks 42`, then "PR #42 is ready to merge." now names the short
CI surface and does not blame the subagent.

Mutation-checked against the restored 40/40:

    partial dropped from the evidence set  ->  39/40
    tie branch removed                     ->  39/40
    partial reason removed                 ->  39/40
    subagent reason removed                ->  39/40

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): the push reason must not assert a complete read that never ran

Round 4. The argmax rewrite carried the push reason's text over from the
guarded form it replaced, and dropped the guard with it. That text opens
"A complete instrument read is in this transcript, but a `git push`
landed after it" -- unconditional on `last_complete >= 0`. So on

    gh pr checks 100  ->  git push  ->  "#100 is good to merge"

with no `check-pr-fully-clean.py` anywhere, the WARN blamed a complete
read that does not exist. The true account is that a short CI surface
was outdated by the push.

Splits the reason in two, one sentence per case, and adds the missing
one. Also pins two things the reviewer found resting on nothing:

- `_relevant_last_subagent`'s strict `idx > last_partial`. A subagent
  report sharing an index with a partial reading is not after it, so it
  stays outside the window and the original case still BLOCKs. Loosening
  to `>=` flips that input to WARN, and until now nothing noticed.
- The `len(kinds) > 1` conjunct on the tie branch, which exhaustive
  enumeration over the 920 reachable states shows is inert -- the early
  return forecloses `complete` being the sole newest kind. Removed, with
  a comment recording why reaching that line means a tie.

Mutation-checked against the restored 42/42:

    push sentence ungated          ->  41/42
    no-complete-read reason gone   ->  41/42
    `in_window` loosened to `>=`   ->  41/42

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): label the claim's own PR, not the first one in the message

Round 5. `_pr_label` took the first `#N` anywhere in the message, so

    "#100 was closed as a duplicate. #200 is green, awaiting your merge."

reported a claim "about #100" and handed back a remediation command
pointed at the wrong PR -- on both the BLOCK and WARN paths. A guard that
tells you to run the instrument is worth little if it names the wrong
argument.

Takes the nearest reference at or before the matched claim phrase, since
a claim's subject usually precedes it, falling back to the nearest one
after and then to the old behaviour.

Mutation-checked against the restored 43/43:

    _pr_label called without the match  ->  42/43

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): bound the PR label, and decline to name one outside the window

Round 6. The nearest-before heuristic was unbounded, so it reached across
a whole message to grab a reference the text itself called unrelated, and
an issue number sitting before the claim outranked the PR the claim was
about. Both produce a confident wrong label in the remediation command
the user is being told to run.

No heuristic over free text picks right every time, so this one stops
trying to. It takes the nearest reference within 120 characters of the
claim phrase, and otherwise returns "the PR you named". A vague label the
reader resolves themselves is strictly better than a precise one pointing
at the wrong PR: the first costs a moment, the second sends the
instrument somewhere it will answer a question nobody asked.

Mutation-checked against the restored 44/44:

    _LABEL_WINDOW unbounded  ->  43/44

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): resolve the claim's subject once, so two call sites cannot disagree

Round 7. `claim_pr_refs` took every `#N` in the message while `_pr_label`,
added two rounds earlier, took a windowed reference near the claim. Two
notions of "what this claim is about", derived from the same text, and
the disagreement was exploitable:

    dispatch(#100) -> report(#100) -> gh pr checks 651
                   -> "#100 was closed as a duplicate. #651 is fully clean."

The #100 subagent matched `claim_pr_refs`, so `last_subagent >= 0`
flipped `is_original_ci_case` to False and the canonical BLOCK case only
WARNed -- reopening round 1's regression for any message naming more than
one PR. A multi-PR status recap is exactly the shape `_pr_label`'s own
tests model, so the guard was defeated by the situation it had just been
taught about.

Resolves the subject once, via `_pr_label`, and derives `claim_pr_refs`
from it. This is round 3's lesson again: the defect was two things
allowed to disagree, so the fix removes the possibility rather than
aligning them.

Mutation-checked against the restored 46/46:

    claim_pr_refs back to whole-message refs  ->  44/46
    (both new cases fail: the #200 claim warns off a #100 subagent, and
     the canonical #651 BLOCK downgrades to a WARN)

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): stop free-text PR matching from deciding BLOCK at all

Round 8. Round 7 narrowed `claim_pr_refs` to the single labelled PR to
stop an unrelated subagent downgrading the canonical BLOCK case. That
purchased the fix with a worse failure: on

    dispatch(#500) -> report(#500)
        -> "#500: implemented the changes over in #501 as a follow-on.
            It's fully clean."

the label resolves to #501, the #500 subagent stops matching, and with no
CI reading to supply a timing match the guard goes SILENT -- on exactly
the shape the extension exists to catch.

Neither width works, because the two consumers want opposite things. The
LABEL needs one PR, conservatively, since it goes into the command the
user runs. The MATCH needs all plausible referents, permissively, since
it only asks whether a subagent touched something this claim is about.

So separate what each may decide, rather than trying to find one width
that serves both:

- `subagent_timed` -- landed after the CI reading, pure ordering, no text
  heuristic -- is now the ONLY thing that can suppress the BLOCK. Free-text
  subject attribution is not reliably decidable, so it must not decide
  whether the canonical case blocks.
- `last_subagent` stays permissive over a window around the claim and
  feeds only the reasons and `reading_needed_since`, whose worst outcome
  is a WARN. This hook family accepts noise over silence.

Three existing expectations changed, deliberately, and each is now
annotated with why: a same-PR subagent before the reading blocks rather
than warns, a nearby unrelated subagent warns rather than staying silent,
and one content case moved to merge-ready vocabulary to stay on the WARN
path it was written to check.

`last_partial >= 0` returns to `is_original_ci_case`, and its history is
worth keeping. Round 4 proved it inert by exhaustive enumeration, and it
was -- while the line read `last_subagent < 0`, which the early return
made imply it. Keying on `subagent_timed` breaks the implication and the
conjunct is load-bearing again. An inertness proof is a statement about
the surrounding guards, not about the conjunct.

Mutation-checked against the restored 48/48:

    BLOCK routed through the permissive match again  ->  45/48
    `last_partial >= 0` dropped                      ->  40/48
    matching narrowed to the single label            ->  45/48
    matching widened to the whole message            ->  47/48

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): hedge the subagent reason when the match is only proximity

Round 9. The routing held up, but the WARN's sentence did not. On

    dispatch(#100) -> report(#100)
        -> "#100 was closed as a duplicate. #200 is fully clean."

the message correctly labelled the claim as being about #200 and then
asserted that "the most recent evidence in this transcript for that
claim is a dispatched subagent's OWN report" -- of a subagent that
worked only on #100, in a message whose own text says #100 is closed and
unrelated. A reader could go audit the wrong PR's evidence chain.

The permissive window is right for deciding whether to look; it is not
evidence of what the claim rests on. So the sentence now depends on how
the subagent was matched:

- Matched by ordering, or working on the PR the claim names: assert.
  Both are facts the transcript settles.
- Matched only by a nearby reference to some other PR: hedge. It says a
  report concerns a PR mentioned alongside the claim, that whether the
  claim rests on it is not something the transcript settles, and what
  follows if it does.

This is the round-3 lesson at the level of prose rather than control
flow: derive what you say from what you actually know, instead of from
the condition that happened to fire.

Mutation-checked against the restored 49/49:

    hedged variant removed             ->  48/49
    label check dropped from on-topic  ->  48/49

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hooks): ordering settles when a report arrived, not what it concerns

Round 10. The previous commit's `subagent_on_topic` kept an ordering
disjunct -- `last_subagent == subagent_timed` -- and that arm carries no
topical check at all. So the round-9 false sentence came back through the
timing path instead of the proximity one, on the repo's own fixture whose
dispatch prompt literally reads "look into unrelated task #9999, nothing
to do with 651":

    gh pr checks 651 -> dispatch(#9999) -> report(#9999)
                     -> "#651 is fully clean at a5f4f3f2."

The WARN named that report as "the most recent evidence in this
transcript for that claim". It is the newest event, and it concerns
nothing the claim mentions.

The disjunct is gone. Topic is decided by topic: does the newest relevant
subagent's own refs contain the PR this claim names. Ordering still
decides relevance and still decides the BLOCK routing, where it is the
right instrument because it needs no text heuristic -- it just cannot
also underwrite a sentence about subject matter.

The existing case over that fixture pair asserted only `decision ==
"warn"`, so the wording was unpinned; a content case now covers it.

Mutation-checked against the restored 50/50:

    ordering disjunct restored  ->  49/50

Refs #3472

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.

d-morrison added a commit to Morrison-Lab/ai-config that referenced this pull request Sep 10, 2026
…is a claim too (#3468)

* ums(verify-the-right-artifact): a reviewer's own counter-measurement is a claim too

Measured 2026-09-09 driving d-morrison/macros#87: an adversarial-reviewer
subagent filed a factual-error finding against a commit message's
verification claim ("verified through `pandoc -t latex`"). The reviewer's
own counter-test appended `\v0 \v1 \v{x}` to a document with NO macro
definitions, observed pandoc pass every token through unexpanded, and
concluded pandoc never expands TeX macros in math mode -- so the original
claim could not possibly have discriminated a working macro file from a
broken one.

The reviewer's measurement was real and its reasoning from it was valid.
It omitted a precondition: pandoc's `latex_macros` extension only expands
macros defined in the same document, so the reviewer tested the null case
and read it as the general case. Re-measuring with the precondition
restored produces the actual discriminator (a three-row table), which is
now embedded in the fix's own PR body.

Adds a new section to verify-the-right-artifact.md making two points: (1)
`dont-take-my-word-for-it` applies to reviewers, not just to humans and
documents -- an accepted or rebutted finding both require re-measuring;
(2) the fix for a vague verification claim is to put the discriminating
measurement, including the null row, into the durable artifact rather than
arguing it out in a PR comment where it dies with the thread. Cross-links
from dont-take-my-word-for-it.md's AI-to-AI hallucination-loop bullet.

Closes #3467

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: semantic line breaks

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ums: fix table cell, redundancy framing, and Do/Don't pairing

Addresses adversarial-reviewer findings on the prior commit:
- The 'no definitions present' row's \v{x} cell was wrong (claimed
  absent; independently re-verified against d-morrison/macros#87's
  actual branches with quarto pandoc -t latex: it passes through
  unexpanded like \v0, matching the other two rows).
- Reframed the section's opening: it is a plain artifact substitution
  (shape 1), not an instance of the immediately preceding "scoped
  narrower" section, and says so explicitly instead of using
  "identical substitution" ambiguously right after that section.
- Restructured the Do/Don't list into three matched pattern/anti-pattern
  pairs per CLAUDE.md's pairing convention (was 3 Do / 2 Don't).
- Moved the dont-take-my-word-for-it.md cross-link from the "AI-to-AI
  hallucination loops" bullet (about ungrounded claims) to "The illusion
  of prior verification" (about false confidence from a real signal),
  which fits the reviewer's-own-counter-test case better.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ums: drop imprecise shape-1 label, restore untouched bullet's line wrap

Addresses second-round adversarial-reviewer findings:
- 'shape 1' (cached/stale copy) doesn't fit a document that never had
  the precondition rather than one that is merely stale; drop the
  specific number and just say it's the same kind of substitution the
  four shapes describe, without forcing it into one of them.
- The AI-to-AI-hallucination-loops bullet's line wrapping was
  incidentally flattened by the sembr tool while editing the adjacent
  bullet, though its wording didn't change; restored its original
  4-line wrap per semantic-line-breaks.md's preserve-existing-breaks
  rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ums: wrap the two new sentences to match the file's sibling bullets

Third-round adversarial-reviewer finding: the two new sentences added
to dont-take-my-word-for-it.md's "illusion of prior verification"
bullet were left as single unwrapped lines (438 and 263 chars) while
every sibling bullet in the same list wraps at ~80 chars per
semantic-line-breaks.md -- an inconsistency within one bullet list,
distinct from verify-the-right-artifact.md's already-common long
single-sentence lines elsewhere in that file, which needed no change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

\def\v0 and \def\v1 define delimited-parameter versions of \v, breaking \v0 and \v{x}

2 participants